/**
	@author Maciej Suminski
	@version 1.0

	Projekt VoD-ka
	Klasa obslugujaca protokol RTSP i dekodujaca MPEG. Wykorzystuje biblioteke FFmpeg <http://ffmpeg.org>
*/

#include "video.hpp"

/**
    @brief Konstruktor podstawowy. Podczas tworzenia obiektu nastepuje polaczenie ze strumieniem wideo.
    @param address  Adres serwera RTSP w postaci "rtsp://host.domena/strumien"
*/
Video::Video(string address)
{
    // Rejestracja wszystkich formatow i kodekow
    av_register_all();

    // Otwarcie strumienia wideo
    if(av_open_input_file(&pFormatCtx, address.c_str(), NULL, 0, NULL) != 0) {
        throw string("Nie można otworzyć strumienia");
    }

    // Pobranie informacji o strumieniu
    if(av_find_stream_info(pFormatCtx) < 0) {
        throw string("Brak informacji o strumieniu");
    }

    // Szukanie pierwszego strumienia wideo
    videoStream = -1;
    for(unsigned int i = 0; i < pFormatCtx->nb_streams; i++) {
        if(pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    }
    if(videoStream == -1) {
        throw string("Brak strumienia wideo");
    }

    // Ustawienie wskaznika na kontekst kodeka dla strumienia wideo
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;

    // Szukanie dekodera dla strumienia
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec == NULL) {
        throw string("Brak właściwego kodeka");
    }

    pSWSContext = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                 pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
                                 PIX_FMT_RGB24, SWS_BICUBIC, 0, 0, 0);
    width = pCodecCtx->width;
    height = pCodecCtx->height;

    // Otwarcie kodeka
    if(avcodec_open(pCodecCtx, pCodec) < 0) {
        throw string("Nie można otworzyć kodeka");
    }

    // Alokacja ramek wideo
    pFrame = avcodec_alloc_frame();
    pFrameRGB = avcodec_alloc_frame();

    // Okreslenie rozmiaru potrzebnego bufora na zdekodowane dane + alokacja bufora
    int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
    buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));

    avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
}

/**
    @brief Destruktor. Zwalnia wszystko.
*/
Video::~Video()
{
    // Zwolnij ramki i bufor
    av_free(pFrame);
    av_free(pFrameRGB);
    av_free(buffer);

    // Zamknij kodek
    avcodec_close(pCodecCtx);

    // Zamknij plik wideo
    av_close_input_file(pFormatCtx);
}

/**
    @brief Zwraca szerokosc ramki odtwarzanego strumienia wideo.
    @return 0 w przypadku bledu odtwarzania, w przeciwnym wypadku - szerokosc ramki odtwarzanego strumienia w pikselach.
*/
unsigned int Video::getWidth()
{
    return width;
}

/**
    @brief Zwraca wysokosc ramki odtwarzanego strumienia wideo.
    @return 0 w przypadku bledu odtwarzania, w przeciwnym wypadku - szerokosc ramki odtwarzanego strumienia w pikselach.
*/
unsigned int Video::getHeight()
{
    return height;
}

/**
    @brief Dekoduje kolejna ramke strumienia wideo i zwraca obiekt wxBitmap.
    @return Kolejna zdekodowana ramka strumienia wideo.
*/
wxBitmap Video::getFrame()
{
    decodeFrame();
    wxImage tmpImage(width, height, buffer, true);
    wxBitmap tmpBitmap(tmpImage);

    return tmpBitmap;
}

/**
    @brief Funkcja dekoduje strumien wideo. Po odebraniu calej ramki dekoduje ja do bufora.
*/
void Video::decodeFrame()
{
    int frameFinished;

    while(av_read_frame(pFormatCtx, &packet) >= 0) {
        // Czy pakiet pochodzi ze strumienia wideo?
        if(packet.stream_index == videoStream) {
            // Dekodowanie ramki wideo
            avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);

            // Czy zdekodowano cala ramke?
            if(frameFinished) {
                sws_scale(pSWSContext, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
                av_free_packet(&packet);
                return;
            }
        }

        // Zwolnij pakiet alokowany przez av_read_frame
        av_free_packet(&packet);
    }

    throw string("Błąd podczas odtwarzania strumienia");
}
